package com.junweilife.gridpuzzle;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.border.EmptyBorder;

class PuzzleFrame extends JFrame {
	private final int width = 600;
	private final int height = 600;
	private int rank = 3;
	private int randomIteration = 1000;
	private int matchedButtonCount;
	private int expectationMatchedButtonCount;
	private JPanel panel;
	private PuzzleButton[][] buttonGrid;

	/**
	 * Create the frame.
	 */
	public PuzzleFrame() {
		super.setTitle("Puzzle Game");
		super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		super.setBounds(150, 150, this.width, this.height);
		super.setResizable(false);
		
//		  建立元件
		JMenuItem menuItemNew = new JMenuItem("New Game");
		JMenuItem menuItemRank3 = new JMenuItem("3");
		JMenuItem menuItemRank4 = new JMenuItem("4");
		JMenuItem menuItemRank5 = new JMenuItem("5");
		JMenuItem menuItemCustomRank = new JMenuItem("Custom");
		JMenuItem menuItemExit = new JMenuItem("Exit");
		JMenuItem menuItemAbout = new JMenuItem("About");
		JSeparator separator1 = new JSeparator();
		JSeparator separator2 = new JSeparator();
		JMenu menuGame = new JMenu("Game");
		JMenu menuHelp = new JMenu("Help");
		JMenu menuRank = new JMenu("Level");
		JMenuBar menuBar = new JMenuBar();
		
//		  加入傾聽者
		menuItemNew.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				int newGameOptionReply = new JOptionPane().showConfirmDialog(
			        null,
			        "要結束嗎?",
			        "放棄",
			        JOptionPane.YES_NO_OPTION
		        );
				
				if (newGameOptionReply != JOptionPane.YES_OPTION) {
					return;
				}
				
				newGame();
			}
		});
		
		ActionListener rankListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
			    int rankOptionReply = new JOptionPane().showConfirmDialog(
			        null,
			        "要結束嗎?",
			        "放棄",
			        JOptionPane.YES_NO_OPTION
		        );
				
				if (rankOptionReply != JOptionPane.YES_OPTION) {
					return;
				}
				
				rank = Integer.parseInt(((JMenuItem) event.getSource()).getText());
				newGame();
			}
		};
		menuItemRank3.addActionListener(rankListener);
		menuItemRank4.addActionListener(rankListener);
		menuItemRank5.addActionListener(rankListener);
		
		menuItemCustomRank.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
//			      while (true) 使用者輸入發生例外時，會再次詢問
			    while (true) {
			        try {
	                    String strCustomRank = new JOptionPane().showInputDialog(
	                        null,
	                        "請輸入3 ~ 9數字：",
	                        "自訂",
	                        JOptionPane.PLAIN_MESSAGE
	                    );
	                    
	                    if (strCustomRank == null) {
	                        throw new Exception("empty input");
	                    }
	                    
	                    int customRank = Integer.parseInt(strCustomRank);
	                    if (customRank<3 || customRank>9) {
	                        throw new Exception("value is out of range");
	                    }
	                    
	                    rank = customRank;
	                    newGame();
	                    break;
	                }
	                catch(Exception ex) {
	                    ex.printStackTrace();
	                }
			    }
			}		
		});
		
		menuItemExit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				System.exit(0);
			}			
		});
		
		menuItemAbout.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				new JOptionPane().showMessageDialog(
			        null,
					"作者：俊威\n版本：1.0.1\n完成日期：2015/04/13",
					"關於",
					JOptionPane.INFORMATION_MESSAGE
				);
			}
		});
		this.addKeyListener(new PuzzleKeyAdapter());
		
//		  加入元件至視窗
		menuGame.add(menuItemNew);
		menuGame.add(separator1);
		menuRank.add(menuItemRank3);
		menuRank.add(menuItemRank4);
		menuRank.add(menuItemRank5);
		menuRank.add(menuItemCustomRank);
		menuGame.add(menuRank);
		menuGame.add(separator2);
		menuGame.add(menuItemExit);
		menuHelp.add(menuItemAbout);
		menuBar.add(menuGame);
		menuBar.add(menuHelp);
		setJMenuBar(menuBar);
		
		this.newGame();
	}
	
	public void newGame() {
		this.panel = new JPanel();
		this.panel.setBorder(new EmptyBorder(3, 3, 3, 3));
		this.panel.setLayout(new GridLayout(this.rank, this.rank, 2, 2));
		this.panel.setBackground(Color.GRAY);
		this.panel.setFocusable(false);
		
		PuzzleActionAdapter buttonListener = new PuzzleActionAdapter();
		this.arrangeButton(buttonListener, this.panel);
		this.randomButton();
		this.expectationMatchedButtonCount = this.rank * this.rank;
		this.calculateMatchedButtonCount();
        
		super.setContentPane(this.panel);
		super.setVisible(true);
	}
	
	private void arrangeButton(PuzzleActionAdapter buttonListener, JPanel panel) {
		int serialNumber = 1;
		buttonGrid = new PuzzleButton[this.rank][this.rank];
		
		for (int i=0; i<buttonGrid.length; i++) {
			for (int j=0; j<buttonGrid.length; j++) {
			    buttonGrid[i][j] = new PuzzleButton(Integer.toString(i * this.rank + (j + 1)));
			    buttonGrid[i][j].setName(Integer.toString(serialNumber++));
			    this.addButtonListener(buttonGrid[i][j], buttonListener);
			    this.appendButton(panel, buttonGrid[i][j]);
			}
		}
		buttonGrid[buttonGrid.length-1][buttonGrid.length-1].setVisible(false);
	}
	
	private void randomButton() {
        Random random = new Random();
        int r = this.rank - 1;
        int c = this.rank - 1;
        int NEWScount = 4;
        int NR = r + 1, NC = c;
        int SR = r - 1, SC = c;
        int WR = r,     WC = c - 1;
        int ER = r,     EC = c + 1;
        
        for(int i=0; i<this.randomIteration; i++) {
            while (true) {
                int direction = random.nextInt(NEWScount);
                boolean isSwapped = false;
                
                switch (direction) {
                    case 0:
                        if ( (NR >= 0 && NR < this.rank) && (NC >= 0 && NC < this.rank) ) {
                            this.swap(buttonGrid[r][c], buttonGrid[NR][NC]);
                            r = NR;
                            c = NC;
                            isSwapped = true;
                        }
                        break;
                    case 1:
                        if ( (ER >= 0 && ER < this.rank) && (EC >= 0 && EC < this.rank) ) {
                            this.swap(buttonGrid[r][c], buttonGrid[ER][EC]);
                            r = ER;
                            c = EC;
                            isSwapped = true;
                        }
                        break;
                    case 2:
                        if ( (WR >= 0 && WR < this.rank) && (WC >= 0 && WC < this.rank) ) {
                            this.swap(buttonGrid[r][c], buttonGrid[WR][WC]);
                            r = WR;
                            c = WC;
                            isSwapped = true;
                        }
                        break;
                    case 3:
                        if ( (SR >= 0 && SR < this.rank) && (SC >= 0 && SC < this.rank) ) {
                            this.swap(buttonGrid[r][c], buttonGrid[SR][SC]);
                            r = SR;
                            c = SC;
                            isSwapped = true;
                        }
                        break;
                }
                
                if (isSwapped) {
                    NR = r + 1;
                    NC = c;
                    SR = r - 1;
                    SC = c;
                    WR = r;
                    WC = c - 1;
                    ER = r;
                    EC = c + 1;
                    break;
                }
            }
        }
    }
	
	private void calculateMatchedButtonCount() {
	    this.matchedButtonCount = 0;
	    for (PuzzleButton[] buttonArray : buttonGrid) {
            for (PuzzleButton button : buttonArray) {
                if (button.getName().equals(button.getText())) {
                    this.matchedButtonCount++;
                }
            }
        }
	}
	
	private void addButtonListener(PuzzleButton button, PuzzleActionAdapter buttonListener) {
	    button.addActionListener(buttonListener);
	}
	
	private void appendButton(JPanel panel, PuzzleButton button) {
	    panel.add(button);
	}
	
	private void determineResult() {
		if (this.matchedButtonCount != this.expectationMatchedButtonCount) {
		    return;
		}
		
		for (PuzzleButton[] buttonArray : buttonGrid) {
            for (PuzzleButton button : buttonArray) {
                button.setEnabled(false);
            }
        }
		
        new JOptionPane().showMessageDialog(null, "666", "congrats!!", JOptionPane.INFORMATION_MESSAGE);
	}
	
	private void updateMatchedButtonCountBeforeSwap(PuzzleButton button, PuzzleButton adjacentButton) {
	    if (button.getName().equals(button.getText())) {
	        this.matchedButtonCount--;
	    }
	    if (adjacentButton.getName().equals(adjacentButton.getText())) {
            this.matchedButtonCount--;
        }
    }
	
	private void updateMatchedButtonCountAfterSwap(PuzzleButton button, PuzzleButton adjacentButton) {
        if (button.getName().equals(button.getText())) {
            this.matchedButtonCount++;
        }
        if (adjacentButton.getName().equals(adjacentButton.getText())) {
            this.matchedButtonCount++;
        }
    }
	
	private void checkButtonGridBoundAndUpdateMatchedButtonCount(Object obj) {
	    PuzzleButton button = (PuzzleButton) obj;
//	      row = (buttonSerialNumber - 1) / rank, col = buttonSerialNumber % rank - 1;
		int buttonSerialNumber = Integer.parseInt(button.getName()) - 1;
		int row = buttonSerialNumber / this.rank, col = buttonSerialNumber % this.rank;
		int NR = row + 1, NC = col;
		int SR = row - 1, SC = col;
		int WR = row,     WC = col - 1;
		int ER = row,     EC = col + 1;
		
		if ( (NR >= 0 && NR < this.rank) && (NC >= 0 && NC < this.rank) ) {
			if (!buttonGrid[NR][NC].isVisible()) {
			    this.updateMatchedButtonCountBeforeSwap(button, buttonGrid[NR][NC]);
				this.swap(button, buttonGrid[NR][NC]);
				this.updateMatchedButtonCountAfterSwap(button, buttonGrid[NR][NC]);
			}
		}
		if ( (SR >= 0 && SR < this.rank) && (SC >= 0 && SC < this.rank) ) {
			if (!buttonGrid[SR][SC].isVisible()) {
			    this.updateMatchedButtonCountBeforeSwap(button, buttonGrid[SR][SC]);
			    this.swap(button, buttonGrid[SR][SC]);
			    this.updateMatchedButtonCountAfterSwap(button, buttonGrid[SR][SC]);
			}
		}
		if ( (WR >= 0 && WR < this.rank) && (WC >= 0 && WC < this.rank) ) {
			if (!buttonGrid[WR][WC].isVisible()) {
			    this.updateMatchedButtonCountBeforeSwap(button, buttonGrid[WR][WC]);
			    this.swap(button, buttonGrid[WR][WC]);
			    this.updateMatchedButtonCountAfterSwap(button, buttonGrid[WR][WC]);
			}
		}
		if ( (ER >= 0 && ER < this.rank) && (EC >= 0 && EC < this.rank) ) {
			if (!buttonGrid[ER][EC].isVisible()) {
			    this.updateMatchedButtonCountBeforeSwap(button, buttonGrid[ER][EC]);
			    this.swap(button, buttonGrid[ER][EC]);
			    this.updateMatchedButtonCountAfterSwap(button, buttonGrid[ER][EC]);
			}
		}
	}
	
	private void swap(PuzzleButton button, PuzzleButton adjacentButton) {
	    PuzzleButton tempButton = new PuzzleButton(button.getText());
	    tempButton.setVisible(button.isVisible());
		button.setText(adjacentButton.getText());
		button.setVisible(adjacentButton.isVisible());				
		adjacentButton.setText(tempButton.getText());
		adjacentButton.setVisible(tempButton.isVisible());
	}
	
	private class PuzzleActionAdapter implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
		    PuzzleButton tempButton = (PuzzleButton) event.getSource();
		    checkButtonGridBoundAndUpdateMatchedButtonCount(tempButton);
			determineResult();
		}
	}
	
	private class PuzzleKeyAdapter implements KeyListener {
		@Override
		public void keyPressed(KeyEvent event) {
			try {
				int keyASCII = event.getKeyCode();
				String length = Character.toString((char) keyASCII);
				rank = Integer.parseInt(length);
				
				if (keyASCII == event.VK_3 || keyASCII == event.VK_4 || keyASCII == event.VK_5) {
					newGame();
				}
				
//				  development
				if (keyASCII == event.VK_2) {
                    newGame();
                }
			}
			catch(Exception ex) {
				ex.printStackTrace();
			}
		}
		
		@Override public void keyReleased(KeyEvent event) {
		}
		
		@Override public void keyTyped(KeyEvent event) {
		}		
	}

}
